#!/usr/bin/python2.4
#
# Copyright 2009 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Meta handler for managing all admin functionality."""

# Python
import os
import logging
import urlparse
import datetime
import time
import sys

# Cruise Control
import base_handler
from third_party.feedparser import feedparser

# AppEngine
from google.appengine.ext.webapp import template

_EDITABLE_ACTIONS = ['new', 'edit']

_BYTES_PER_MB = 1048576.0  # Bytes

# The datastore has a hard 1MB limit for entities. We limit the blob size to
# make sure there is room for all the other data in the entity.
_BLOB_MB_MAX = 0.95

class AdminHandler(base_handler.BaseHandler):
  """Base handler for admin functions."""

  def RenderPage(self, page):
    """Returns the desired page to the user.

    Also, checks to see if an admin has requested to view the reqested page
    using an alternate template.

    Args:
      page: string HTML template file name that is to be displayed for the
        user.
    """
    site_template = 'templates/' + page

    if self.http_status_code != 200:
      self.error(self.http_status_code)
    path = os.path.join(os.path.dirname(__file__), site_template)
    logging.warning("warning: " + path)
    self.response.out.write(template.render(path, self.html_vars))


class Settings(AdminHandler):
  """Handler for managing blog properties."""

  def Call(self):
    if self.request_method == 'POST':
      save = self.params.get('save')
      done = self.params.get('done')
      cancel = self.params.get('cancel')
      blog = self.params.get('blog')
      if blog and (save or done):
        blog, warnings = self.db.SetBlog(self.params)
      if done and not warnings:
        self.GoToPage('/admin/posts')
        return

      if warnings:
        self.html_vars['warnings'] = warnings
      else:
        self.blog = self.db.GetBlog()
        self.html_vars['blog'] = self.blog
        self.blog_template = self.blog.template
        self.html_vars['confirmation'] = True

    self.RenderPage('admin_settings.html')


class Links(AdminHandler):
  """Handler for managing links and link properties."""

  def Call(self):
    if self.request_method == 'POST':
      do_new_link = self.params.get('new')
      do_edit_links = self.params.get('edit')
      warnings = {}

      # Creating a new link
      if do_new_link:
        self.html_vars['new'] = True
        link, warnings = self.db.SetLink(self.params)
        if warnings:
          self.html_vars['warnings'] = warnings
          self.html_vars['link'] = link
        else:
          self.html_vars['confirmation'] = True

      # Editing Existing links
      elif do_edit_links:
        link_edits = {}
        deletes = []
        for param in self.params:
          if param.startswith('delete_'):
            deletes.append(self.params[param])
            continue
          if param.count('_'):
            param_pieces = param.split('_')
            key = param_pieces[-1]
            if key not in link_edits:
              # We add key here in case there are warnings
              link_edits[key] = {'link': key, 'key': key}

            if param.startswith('anchor_'):
              # Keep the links in the same order they are in the UI.
              pos = param_pieces[1]
              link_edits[key]['pos'] = int(pos)
              link_edits[key]['anchor'] = self.params[param]
            if param.startswith('url_'):
              link_edits[key]['url'] = self.params[param]
            if param.startswith('nofollow_'):
              link_edits[key]['nofollow'] = self.params[param]

        edited_links = []
        for link in link_edits:
          link_edit, link_warnings = self.db.SetLink(link_edits[link])
          if link_warnings:
            for warning in link_warnings:
              if warning in warnings:
                warnings[warning].append(link)
              else:
                warnings[warning] = [link]
          edited_links.append(link_edits[link])

        if warnings:
          edited_links.sort(lambda x, y: x['pos'] - y['pos'])
          self.html_vars['warnings'] = warnings
          self.html_vars['links'] = edited_links
        else:
          for key in deletes:
            self.db.DeleteLink(key)
          self.html_vars['confirmation'] = True


    if 'links' not in self.html_vars:
      self.html_vars['links'] = self.db.GetLinks()

    self.RenderPage('admin_links.html')


class Post(AdminHandler):
  """Handler for managing posts and post properties."""

  def Call(self):
    if len(self.path_list) == 1:
      self.GoToPage('/admin/posts')
      return

    action = self.request.get('action', default_value=False)
    post = self.request.get('post', default_value=False)
    if action == 'delete' and post:
      delete_post = self.db.DeletePost(post)
      self.GoToPage('/admin/posts')
      return

    if action in ['new', 'edit']:

      self.html_vars['action'] = True
      save = self.request.get('save', default_value=False)
      done = self.request.get('done', default_value=False)
      cancel = self.request.get('cancel', default_value=False)
      warnings = None

      if save or done and not cancel:
        post, warnings = self.db.SetPost(self.params)
        if warnings:
          self.html_vars['warnings'] = warnings
        else:
          self.html_vars['confirmation'] = True
      elif action == 'edit':
        post = self.db.GetPost(post)

      if (done and not warnings) or cancel:
        self.GoToPage('/admin/posts')
        return

    else:
      post = False

    if post:
      self.html_vars['post'] = post
    else:
      self.html_vars['posts'] = self.db.GetAllPosts()

    self.RenderPage('admin_posts.html')


class Comments(AdminHandler):
  """Handler for managing comments."""

  def Call(self):
    comment_key = self.params.get('comment')
    status = self.params.get('status')
    if comment_key and status:
      self.db.UpdateCommentStatus(comment_key, status)

    unreviewed_comments = self.db.GetUnReviewedComments()

    comments = []
    if unreviewed_comments.count():
      users = []
      for comment in unreviewed_comments:
        users.append(comment.author.key())
      users_comments = self.db.GetCommentsByUsers(users)

      author_comments = {}
      for comment in users_comments:
        if comment.author.key() not in author_comments:
          author_comments[comment.author.key()] = {'approved': [],
                                                   'rejected': []}
        if comment.status == 'approved':
          author_comments[comment.author.key()]['approved'].append(comment)
        if comment.status == 'rejected':
          author_comments[comment.author.key()]['rejected'].append(comment)

      for comment in unreviewed_comments:
        approved_count = len(author_comments[comment.author.key()]['approved'])
        rejected_count = len(author_comments[comment.author.key()]['rejected'])
        user_comment = {'approved': approved_count,
                        'rejected': rejected_count,
                        'key': comment.key()}

        comment_properties = comment.properties()
        for prop in comment_properties:
          user_comment[prop] = getattr(comment, prop)

        comments.append(user_comment)

    self.html_vars['unreviewed_comments'] = comments
    self.RenderPage('admin_comments.html')


class Pages(AdminHandler):
  """Handler for managing pages and page properties."""

  def Call(self):
    action = self.request.get('action', default_value=False)
    page = self.request.get('page', default_value=False)
    if action not in _EDITABLE_ACTIONS:
      if page and action == 'delete':
        self.db.DeletePageByKey(page)
        self.GoToPage('/admin/pages')
        return

    elif action in ['new', 'edit']:
      self.html_vars['action'] = True
      save = self.request.get('save', default_value=False)
      done = self.request.get('done', default_value=False)
      cancel = self.request.get('cancel', default_value=False)

      if (save or done) and not cancel:
        page, warnings = self.db.SetPage(self.params)

        if warnings:
          self.html_vars['warnings'] = warnings
        else:
          self.html_vars['confirmation'] = True
      elif action == 'edit':
        page = self.db.GetPage(page)

      if (done and not warnings) or cancel:
        self.GoToPage(self.path)
        return

    else:
      page = False

    if page:
      self.html_vars['page'] = page
    else:
      self.html_vars['pages'] = self.db.GetPages()
    #Do not display page tabs in admin section
    self.html_vars['published_pages'] = []
    self.RenderPage('admin_pages.html')


class ImportFeed(AdminHandler):
  """Handles parsing feeds and creating pages and posts."""

  def Call(self):
    do_import = self.request.get('do_import', default_value=False)
    feedURL = self.request.get('feedURL',
    default_value='http://feedparser.org/docs/examples/atom10.xml')
    cancel = self.request.get('cancel', default_value=False)
    import_type = self.request.get('import_type', default_value='post')
    path_prefix = self.request.get('path_prefix')

    self.html_vars['do_import'] = do_import
    self.html_vars['feedURL'] = feedURL
    self.html_vars['import_type'] = import_type
    self.html_vars['path_prefix'] = path_prefix

    if self.request_method == 'POST' and not cancel:
      """ Override the sanitizer to accept elements and attributes used for YouTube and Google Video embedding """
      feedparser._HTMLSanitizer.acceptable_elements.extend(['object', 'param', 'embed'])
      feedparser._HTMLSanitizer.acceptable_attributes.extend(['style'])

      parsed_feed = feedparser.parse(feedURL)
      self.html_vars['parsed_feed'] = parsed_feed

      if do_import:
        for entry in parsed_feed.entries:
          """ Map values from Feedparser entries to those used by Cruise Control. """
          new_entry = {}
          new_entry['title'] = entry.title
          new_entry['button_name'] = entry.title
          new_entry['author'] = self.params.get('author')
          new_entry['body'] = entry.content[0].value
          new_entry['is_published'] = True

          """ Set dates using provided dates, feed dates, or use current time, depending on what comes in the feed. """
          if 'published_parsed' in entry:
            new_entry['publish_date'] = time.strftime("%Y%m%d%H%M%S", entry.published_parsed)
          else:
            new_entry['creation_date'] = datetime.datetime.now()
          if 'updated_parsed' in entry:
            new_entry['edit_date'] = time.strftime("%Y%m%d%H%M%S", entry.updated_parsed)
          else:
            new_entry['edit_date'] = new_entry['publish_date']
          if 'created_parsed' in entry:
            new_entry['creation_date'] = time.strftime("%Y%m%d%H%M%S", entry.created_parsed)
            new_entry['draft_date'] = time.strftime("%Y%m%d%H%M%S", entry.created_parsed)
          else:
            new_entry['creation_date'] = new_entry['publish_date']
            new_entry['draft_date'] = new_entry['publish_date']

          """ Determine if feed contained Feeburned URL's (typical of Blogger) and always strip down to the path."""
          if 'feedburner_origlink' in entry:
            tempURL = entry.feedburner_origlink
          else:
            tempURL = entry.link
          tempURL = urlparse.urlparse(tempURL)
          new_entry['path'] = tempURL.path[1:]

          if path_prefix:
            new_entry['path'] = path_prefix + '/' + new_entry['path']

          if import_type == 'post':
            page, warnings = self.db.SetPost(new_entry)
          else:
            page, warnings = self.db.SetPage(new_entry)

          if warnings:
            self.html_vars['warnings'] = warnings
          else:
            self.redirect(import_type)
    self.RenderPage('admin_import_feed.html')


class Media(AdminHandler):
    """Handler for managing images."""

    def Call(self):
        request = self.request
        if request.get('cancel'):
            self.GoToPage('/admin/media')

        action = request.get('action')
        self.html_vars['action'] = action
        key = request.get('key')

        if action == 'delete':
            if key:
                self.db.DeleteMedia(key)
                self.GoToPage('/admin/media')
                return

        elif action == 'edit':
            if key:
                media = self.db.GetMedia(key)

                if request.get('save') or request.get('done'):
                    params = {
                        'title': request.get('title', default_value=''),
                        'caption': request.get('caption', default_value='')}
                    media = self.db.UpdateMedia(key, params)
                    self.html_vars['confirmation'] = True

                if request.get('done'):
                    self.GoToPage('/admin/media')
                    return

                self.html_vars['media'] = media

        elif request.get('upload'):
            uploaded_file = request.POST.get('data')
            try:
                params = {
                    'data': uploaded_file,
                    'content_type': uploaded_file.type,
                    'name': uploaded_file.filename,
                    'size': round(len(request.get('data'))/_BYTES_PER_MB, 2)}
            except AttributeError:
                alert = True

            if 'alert' in locals():
                self.html_vars['alert'] = ("""Upload problem: some properties
                                           could not be parsed.""")
            elif params['size'] > _BLOB_MB_MAX:  # 0.95 MB
                self.html_vars['message'] = ('File too large: %s MB'
                                             % params['size'])
            else:
                if self.db.UploadMedia(params):
                    self.GoToPage('/admin/media')
                    return
                else:
                    self.html_vars['alert'] = ("""Upload failded. Unrecognized
                                               content type: %s.""" 
                                               % params['content_type'])

        else:
            media = self.db.GetAllMedia()
            self.html_vars['media'] = media

        self.RenderPage('admin_media.html')